/*
 * The MIT License
 *
 * Copyright 2014 David Rodenas Pico.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.uoc.sd.fr14.comp;

import edu.uoc.sd.fr14.actors.Clock;
import edu.uoc.sd.fr14.actors.Cluster;
import edu.uoc.sd.fr14.actors.Server;
import edu.uoc.sd.fr14.mesg.Message;
import edu.uoc.sd.fr14.mesg.MessageForCustomer;
import edu.uoc.sd.fr14.mesg.MessageForServer;
import edu.uoc.sd.fr14.model.AbstractServer;
import edu.uoc.sd.fr14.model.Logger;
import edu.uoc.sd.fr14.model.Quorum;
import edu.uoc.sd.fr14.model.ServerFactory;
import edu.uoc.sd.fr14.model.ServerId;
import edu.uoc.sd.fr14.model.ServerState;

import java.util.Collections;
import java.util.Set;

/**
 * A component that holds one server.
 * 
 * Input:
 * - input: input messages for the server
 * 
 * Output:
 * - output: output messages from the server
 * 
 * @author drodenas
 */
public class ServerComponent extends Component {
    
    private static final Logger log = Logger.get("ServerComponent");
    
    private final ServerId me;
    private final Cluster cluster;
    private final AbstractServer server;
    private final Object guard = new Object();
    private volatile boolean connected = true;

    public ServerComponent(final ServerId me, final Set<ServerId> servers, Class<AbstractServer> serverClass, final Clock clock) {
        super(""+me);
        
        if (me == null) throw new NullPointerException("Illegal argument: me cannot be null");
        if (servers == null) throw new NullPointerException("Illegal argument: servers cannot be null");
        if (servers.isEmpty()) throw new IllegalArgumentException("Illegal argument: servers should have at least one server");
        if (!servers.contains(me)) throw new IllegalArgumentException("Illegal argument: me should be contained in the list of servers");
        if (clock == null) throw new NullPointerException("Illegal argument: clock cannot be null");
        
        // create the cluster
        cluster = new Cluster() {
            @Override
            public Clock getClock() {
                return clock;
            }

            @Override
            public Quorum getQuorum() {
                return Quorum.createFromTotal(servers.size());
            }

            @Override
            public void sendToCustomer(Message message) {
                if (message == null) throw new NullPointerException("Illlegal argument: message cannot be null");
                if (!(message instanceof MessageForCustomer)) throw new ClassCastException("Illegal argument: message should be a valid message for a customer");

                deliver(message);
            }

            @Override
            public void sendToProxy(Message message) {
                if (message == null) throw new NullPointerException("Illlegal argument: message cannot be null");
                if (!(message instanceof MessageForCustomer) && !(message instanceof MessageForServer)) throw new ClassCastException("Illegal argument: message should be a valid message for a proxy");

                if (message instanceof MessageForServer) {
                    sendToServer(message);
                } else {
                    sendToCustomer(message);
                }
            }

            @Override
            public void sendToServer(Message message) {
                if (message == null) throw new NullPointerException("Illlegal argument: message cannot be null");
                if (!(message instanceof MessageForServer)) throw new ClassCastException("Illegal argument: message should be a valid message for a server");

                deliver(message);
            }

            @Override
            public Set<ServerId> getServers() {
                return Collections.unmodifiableSet(servers);
            }
        };
        
        this.me = me;
        server = ServerFactory.create(me, serverClass, cluster);
    }

    @Override
    protected void process(Message message) {
        Server current = server;

        if (!connected) return;
        if (message == null || current == null) return;                
        if (!message.getTarget().equals(me)) return;
        if (!(message instanceof MessageForServer)) return;

        synchronized (guard) {
	        log.debug(me, "received '"+message+"'");
	        ((MessageForServer) message).apply(current);
        }
    }

    public Cluster getCluster() {
        return cluster;
    }

    @Override
    protected void deliver(Message message) {
        if (!connected) return;
        log.debug(me, "deliver '"+message+"'");
        super.deliver(message);
    }

    @Override
    public void stop() {
        System.out.println(me+"\n"+server.getServerState()+"\n");
        super.stop();
    }

	public ServerState getServerState() {		
        synchronized (guard) {
    		return server.getServerState();
        }
	}

	public void forceSynchronize() {
        synchronized (guard) {
    		server.synchronize();
        }
	}

	public void disconnect() {
        log.debug(me, "disconnect");
		connected = false;
	}
    
	public void connect() {
        log.debug(me, "connect");
		connected = true;
	}
    
}
